/*
 * led_strip.c
 *
 *  Created on: Mar 16, 2021
 *      Author: kychu
 */

#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <math.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/unistd.h>

#include "cJSON.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/rmt.h"
#include "freertos/task.h"

#include "colortf.h"
#include "led_type.h"
#include "led_strip.h"
#include "sdkconfig.h"

static const char *TAG = "leds";

static const char *led_cfg_json_name[] = {
  "number",
  "init_mode",
  "init_h",
  "init_s",
  "init_v"
};

#define RMT_TX_CHANNEL RMT_CHANNEL_0

static int local_mode = 0;
static led_hsv_t local_hsv = {0, 0, 0};
static int local_update = 0;
led_handle_t *leds = NULL;

int led_mode_check(int mode) { return (mode == local_mode) ? 1 : 0; }

static void free_color_mode(led_handle_t *led, const void *args)
{
  // Clear LED strip (turn off all LEDs)
  ESP_ERROR_CHECK(led->strip->clear(leds->strip, 100));
  while(led_mode_check(0)) {
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}

static void pure_color_mode(led_handle_t *led, const void *args)
{
  const led_mode_t *mode = (const led_mode_t *)args;
  led_rgb_t rgb;
  local_update = 1;
  while(led_mode_check(mode->mode_id)) {
    if(local_update == 1) {
      local_update = 0;
      // Build RGB values
      hsv2rgb(&local_hsv, &rgb);
      for(int i = 0; i < led->number; i ++) {
        // Write RGB values to strip driver
        ESP_ERROR_CHECK(led->strip->set_pixel(led->strip, i, rgb.r, rgb.g, rgb.b));
      }
      // Flush RGB values to LEDs
      ESP_ERROR_CHECK(led->strip->refresh(led->strip, 100));
    }
    vTaskDelay(pdMS_TO_TICKS(10));
  }
}

static void breath_color_mode(led_handle_t *led, const void *args)
{
  const led_mode_t *mode = (const led_mode_t *)args;
  led_rgb_t rgb;
  led_hsv_t hsv;
  uint16_t phase = 0;
  while(led_mode_check(mode->mode_id)) {
    hsv.h = local_hsv.h;
    hsv.s = local_hsv.s;
	hsv.v = 4 + 48 * (1.0f - cosf(phase * 0.01745329251994329576923690768489f));
	phase ++; if(phase >= 360) phase = 0;
	// Build RGB values
	hsv2rgb(&hsv, &rgb);
	for(int i = 0; i < led->number; i ++) {
	  // Write RGB values to strip driver
	  ESP_ERROR_CHECK(led->strip->set_pixel(led->strip, i, rgb.r, rgb.g, rgb.b));
	}
	// Flush RGB values to LEDs
	ESP_ERROR_CHECK(led->strip->refresh(led->strip, 100));
	vTaskDelay(pdMS_TO_TICKS(10));
  }
}

static void flow_mode(led_handle_t *led, const void *args, uint8_t *buff, uint16_t len)
{
  const led_mode_t *mode = (const led_mode_t *)args;
  led_rgb_t rgb;
  led_hsv_t hsv;
  int moves = 0;
  while(led_mode_check(mode->mode_id)) {
    hsv.h = local_hsv.h;
    hsv.s = local_hsv.s;
    for(int i = 0; i < led->number; i ++) {
      int idx = (i + moves) % len;
      hsv.v = buff[idx];
      // Build RGB values
      hsv2rgb(&hsv, &rgb);
      // Write RGB values to strip driver
      ESP_ERROR_CHECK(led->strip->set_pixel(led->strip, i, rgb.r, rgb.g, rgb.b));
    }
    // Flush RGB values to LEDs
    ESP_ERROR_CHECK(led->strip->refresh(led->strip, 100));
    vTaskDelay(pdMS_TO_TICKS(50));
    moves ++; if(moves >= len) moves = 0;
  }
}

static void flow_color_mode(led_handle_t *led, const void *args)
{
  uint8_t *buff = calloc(1, 30);
  for(int i = 0; i < 27; i ++) {
	buff[i + 2] = 100 * (26 * i - i * i) / 169;
  }
  flow_mode(led, args, buff, 30);
  free(buff);
}

static void meteor_flow_mode(led_handle_t *led, const void *args)
{
  int len = 40;
  uint8_t *buff = calloc(1, len);
  for(int i = 0; i <= len - 5; i ++) {
    buff[len-5-i] = i * i * 100/((len-5)*(len-5));
  }
  flow_mode(led, args, buff, len);
  free(buff);
}

static void Rainbow_Chase_mode(led_handle_t *led, const void *args)
{
  const led_mode_t *mode = (const led_mode_t *)args;
  led_rgb_t rgb;
  led_hsv_t hsv;
  uint16_t start_rgb = 0;
  while(led_mode_check(mode->mode_id)) {
    for(int i = 0; i < led->number; i ++) {
      hsv.h = i * 6 + start_rgb; // 60 LEDs one cycle
      hsv.s = local_hsv.s;
      hsv.v = local_hsv.v;
      // Build RGB values
      hsv2rgb(&hsv, &rgb);
      // Write RGB values to strip driver
      ESP_ERROR_CHECK(led->strip->set_pixel(led->strip, i, rgb.r, rgb.g, rgb.b));
    }
    // Flush RGB values to LEDs
    ESP_ERROR_CHECK(led->strip->refresh(led->strip, 100));
    vTaskDelay(pdMS_TO_TICKS(10));
    start_rgb += 1;
  }
}

static void color_breath_mode(led_handle_t *led, const void *args)
{
  const led_mode_t *mode = (const led_mode_t *)args;
  led_rgb_t rgb;
  led_hsv_t hsv;
  uint16_t phase = 0;
  while(led_mode_check(mode->mode_id)) {
    hsv.s = local_hsv.s;
	hsv.v = 4 + 48 * (1.0f - cosf(phase * 0.01745329251994329576923690768489f));
	phase ++; if(phase >= 360) phase = 0;
	if(phase % 30 == 0) {
      hsv.h += 1; if(hsv.h >= 360) hsv.h = 0;
	}
	// Build RGB values
	hsv2rgb(&hsv, &rgb);
	for(int i = 0; i < led->number; i ++) {
	  // Write RGB values to strip driver
	  ESP_ERROR_CHECK(led->strip->set_pixel(led->strip, i, rgb.r, rgb.g, rgb.b));
	}
	// Flush RGB values to LEDs
	ESP_ERROR_CHECK(led->strip->refresh(led->strip, 100));
	vTaskDelay(pdMS_TO_TICKS(10));
  }
}

static led_mode_t led_modes[] = {
  {"free color", 0, free_color_mode},
  {"pure color", 1, pure_color_mode},
  {"breath mode", 2, breath_color_mode},
  {"flow mode", 3, flow_color_mode},
  {"single flow", 4, meteor_flow_mode},
  {"rainbow chase", 5, Rainbow_Chase_mode},
  {"color breath", 6, color_breath_mode},
};

static esp_err_t led_strip_init(void)
{
  esp_err_t err = ESP_FAIL;

  rmt_config_t config = RMT_DEFAULT_CONFIG_TX(CONFIG_EXAMPLE_RMT_TX_GPIO, RMT_TX_CHANNEL);
  // set counter clock to 40MHz
  config.clk_div = 2;

  err = rmt_config(&config);
  if(err != ESP_OK) return err;
  err = rmt_driver_install(config.channel, 0, 0);
  if(err != ESP_OK) return err;

  // install ws2812 driver
  led_strip_config_t strip_config = {
    .max_leds = leds->number,
    .dev = (led_strip_dev_t)config.channel,
  };
  leds->strip = led_strip_new_rmt_ws2812(&strip_config);
  if (!leds->strip) {
    ESP_LOGE(TAG, "install WS2812 driver failed");
    return ESP_FAIL;
  }
  // Clear LED strip (turn off all LEDs)
  err = leds->strip->clear(leds->strip, 100);

  return err;
}

void led_strip_task(void *args)
{
  int rd_bytes = 0;
  leds = calloc(1, sizeof(led_handle_t));
  if(leds == NULL) {
    ESP_LOGE(TAG, "no MEM for LED handle!");
    goto exit;
  }
  // initialize parameters
  leds->number = 1;
  local_mode = 1;
  local_hsv.h = 0; // 50% white
  local_hsv.s = 0;
  local_hsv.v = 50;

  // read the configuration
  char filepath[32];
  memset(filepath, 0, 32);
  strlcpy(filepath, args, 32);
  strlcat(filepath, "/led.json", 32);

  FILE *fp = fopen(filepath, "r");
  if(fp == NULL) {
    ESP_LOGW(TAG, "open %s failed, create it!", filepath);
    fp = fopen(filepath, "w");
    if(fp == NULL) {
      ESP_LOGE(TAG, "%s create failed!", filepath);
      goto exit;
    } else {
      cJSON *root = cJSON_CreateObject();
      cJSON_AddNumberToObject(root, led_cfg_json_name[0], leds->number);
      cJSON_AddNumberToObject(root, led_cfg_json_name[1], local_mode);
      cJSON_AddNumberToObject(root, led_cfg_json_name[2], local_hsv.h);
      cJSON_AddNumberToObject(root, led_cfg_json_name[3], local_hsv.s);
      cJSON_AddNumberToObject(root, led_cfg_json_name[4], local_hsv.v);
      const char *sys_info = cJSON_Print(root);
      int len = strlen(sys_info);
      if((len > 0) && len != fwrite(sys_info, 1, len, fp)) {
        ESP_LOGE(TAG, "failed to save configuration");
        unlink(filepath);
        len = -1;
      }
      free((void *)sys_info);
      cJSON_Delete(root);
      fclose(fp);
      if(len < 0) goto exit;
    }
  } else {
    char *buff = calloc(1, 1024);
    if(buff != NULL) {
      if((rd_bytes = fread(buff, 1, 1024, fp)) == -1) {
        ESP_LOGE(TAG, "failed to read %s", filepath);
      } else {
        buff[rd_bytes] = 0;
        cJSON *root = cJSON_Parse(buff);
        leds->number = cJSON_GetObjectItem(root, led_cfg_json_name[0])->valueint;
        ESP_LOGI(TAG, "led number: %d", leds->number);
        local_mode = cJSON_GetObjectItem(root, led_cfg_json_name[1])->valueint;
        ESP_LOGI(TAG, "default mode: %d", local_mode);
        local_hsv.h = cJSON_GetObjectItem(root, led_cfg_json_name[2])->valueint;
        local_hsv.s = cJSON_GetObjectItem(root, led_cfg_json_name[3])->valueint;
        local_hsv.v = cJSON_GetObjectItem(root, led_cfg_json_name[4])->valueint;
        ESP_LOGI(TAG, "default HSV: %d, %d, %d", local_hsv.h, local_hsv.s, local_hsv.v);
        cJSON_Delete(root);
      }
      free(buff);
    } else {
      ESP_LOGE(TAG, "no memory for file read cache");
    }
    fclose(fp);
  }
  if(led_strip_init() != ESP_OK) {
    ESP_LOGE(TAG, "LED strip initialize failed!");
    goto exit;
  }
  vTaskDelay(pdMS_TO_TICKS(200));
  // test all LEDs
  for(int i = 0; i < leds->number; i ++) {
    ESP_ERROR_CHECK(leds->strip->set_pixel(leds->strip, i, 127, 127, 127));
    ESP_ERROR_CHECK(leds->strip->refresh(leds->strip, 100));
    vTaskDelay(pdMS_TO_TICKS(3000/leds->number));
  }
  for(int i = 0; i < leds->number; i ++) {
    ESP_ERROR_CHECK(leds->strip->set_pixel(leds->strip, i, 0, 0, 0));
    ESP_ERROR_CHECK(leds->strip->refresh(leds->strip, 100));
    vTaskDelay(pdMS_TO_TICKS(3000/leds->number));
  }
  vTaskDelay(pdMS_TO_TICKS(500));
  for(int t = 0; t < 50; t ++) {
    for(int i = 0; i < leds->number; i ++) {
      ESP_ERROR_CHECK(leds->strip->set_pixel(leds->strip, i, t*51/20, t*51/20, t*51/20));
    }
    ESP_ERROR_CHECK(leds->strip->refresh(leds->strip, 100));
    vTaskDelay(pdMS_TO_TICKS(20));
  }
  // wait 1s
  vTaskDelay(pdMS_TO_TICKS(1000));

  int force_delay;
  int num = sizeof(led_modes) / sizeof(led_mode_t);
  while(1) {
    force_delay = 1;
    for(int i = 0; i < num; i ++) {
      if(led_modes[i].mode_id == local_mode) {
        ESP_LOGI(TAG, "run led mode %d, %s", led_modes[i].mode_id, led_modes[i].name);
        led_modes[i].call(leds, &led_modes[i]);
        force_delay = 0;
      }
    }
    if(force_delay) {
      vTaskDelay(pdMS_TO_TICKS(10));
    }
  }

exit:
  ESP_LOGI(TAG, "task exit!");
  vTaskDelete(NULL);
}

void led_strip_mode_hsv(int mode, led_hsv_t hsv)
{
   local_mode = mode;
   local_hsv = hsv;
   local_update = 1;
}

int led_get_number(void) { return ((leds != NULL) ? leds->number : 0); }
esp_err_t led_set_number(int n) {
  if(leds == NULL || n < 1 || n > 500) return ESP_FAIL;
  leds->number = n; return ESP_OK;
}

esp_err_t led_config_validate(const char *path)
{
  char filepath[32];
  memset(filepath, 0, 32);
  strlcpy(filepath, path, 32);
  strlcat(filepath, "/led.json", 32);

  FILE *fp = fopen(filepath, "w");
  if(fp == NULL) {
    ESP_LOGE(TAG, "failed to open %s", filepath);
    return ESP_ERR_NOT_FOUND;
  } else {
    cJSON *root = cJSON_CreateObject();
    cJSON_AddNumberToObject(root, led_cfg_json_name[0], leds->number);
    cJSON_AddNumberToObject(root, led_cfg_json_name[1], 1);
    cJSON_AddNumberToObject(root, led_cfg_json_name[2], 0);
    cJSON_AddNumberToObject(root, led_cfg_json_name[3], 0);
    cJSON_AddNumberToObject(root, led_cfg_json_name[4], 50);
    const char *sys_info = cJSON_Print(root);
    int len = strlen(sys_info);
    if((len > 0) && len != fwrite(sys_info, 1, len, fp)) {
      ESP_LOGE(TAG, "failed to save configuration");
      unlink(filepath);
      len = -1;
    }
    free((void *)sys_info);
    cJSON_Delete(root);
    fclose(fp);
    if(len < 0) return ESP_FAIL;
    return ESP_OK;
  }
}
